package com.example.annnotationprocess;

import com.example.annotation.Register;
import com.example.annotation.RegisterInterface;
import com.google.auto.service.AutoService;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.WildcardTypeName;

import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.tools.Diagnostic;

import static javax.lang.model.element.Modifier.PUBLIC;

@AutoService(Processor.class)
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class RegisterProcessor extends AbstractProcessor {
    private Elements elementUtils;
    private Filer filer;
    private TypeMirror typeElement;
    private Map<Integer, Element> registers = new TreeMap<>();
    private Messager messager;
    private String moduleName;

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return Collections.singleton(Register.class.getCanonicalName());
    }

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        elementUtils = processingEnvironment.getElementUtils();
        filer = processingEnvironment.getFiler();

        typeElement = elementUtils.getTypeElement(RegisterInterface.class.getCanonicalName()).asType();

        messager = processingEnvironment.getMessager();

        Map<String, String> options = processingEnv.getOptions();
        if (options != null && !options.isEmpty()) {
            moduleName = options.get("moduleName");
        }

        if (moduleName != null && (!moduleName.isEmpty())) {
            moduleName = moduleName.replaceAll("[^0-9a-zA-Z_]+", "");
            messager.printMessage(Diagnostic.Kind.NOTE, "The user has configuration the module name, it was [" + moduleName + "]");
        } else {
            messager.printMessage(Diagnostic.Kind.ERROR, "These no module name, at 'build.gradle', like :\n" +
                    "apt {\n" +
                    "    arguments {\n" +
                    "        moduleName project.getName();\n" +
                    "    }\n" +
                    "}\n");
            throw new RuntimeException("ARouter::Compiler >>> No module name, for more information, look at gradle log.");
        }

    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        if (set == null || set.isEmpty()) return false;
        Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(Register.class);
        if (elementsAnnotatedWith == null || elementsAnnotatedWith.isEmpty()) return false;

        for (Element element : elementsAnnotatedWith) {
            if (!verify(element)) {
                messager.printMessage(Diagnostic.Kind.ERROR, "A register verify over, its " + element.asType());
                continue;
            }
            messager.printMessage(Diagnostic.Kind.NOTE, element.asType() + " " + element.getSimpleName());
            Register annotation = element.getAnnotation(Register.class);
            Element lastRegister = registers.get(annotation.priority());
            if (lastRegister != null) {
                throw new IllegalArgumentException(
                        String.format(Locale.getDefault(), "More than one interceptors use same priority [%d], They are [%s] and [%s].",
                                annotation.priority(),
                                lastRegister.getSimpleName(),
                                element.getSimpleName())
                );
            }
            registers.put(annotation.priority(), element);

        }

        TypeElement typeElement = elementUtils.getTypeElement(RegisterInterface.class.getCanonicalName());
        ParameterizedTypeName arraylistType = ParameterizedTypeName.get(ClassName.get(List.class),
                ParameterizedTypeName.get(ClassName.get(Class.class), WildcardTypeName.subtypeOf(ClassName.get(typeElement))));

        //parameter ...
        ParameterSpec parameterSpec = ParameterSpec.builder(arraylistType, "registers").build();

        //method loadInto
        MethodSpec.Builder builder = MethodSpec.methodBuilder("loadInto").addModifiers(Modifier.PUBLIC)
                .addParameter(parameterSpec);

        if (registers != null && !registers.isEmpty()) {
            for (Map.Entry<Integer, Element> entry : registers.entrySet()) {
                builder.addStatement("registers.add($T.class)", ClassName.get((TypeElement) entry.getValue()));
            }
        }

        try {
            JavaFile.builder("cn.sit.hawk",
                    TypeSpec.classBuilder("AppModule")
                            .addModifiers(PUBLIC)
                            .addMethod(builder.build())
                            .build()
            ).build().writeTo(filer);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return true;
    }

    private boolean verify(Element element) {
        Register annotation = element.getAnnotation(Register.class);
        return annotation != null && ((TypeElement) element).getInterfaces().contains(typeElement);
    }
}
